﻿using DeepEarth.Core;
using System.Collections.ObjectModel;
using System.Windows;
using System.ComponentModel;
using DeepEarth.Core.Utilities;
using System.Linq;
using DeepEarth.Core.Data;
using System.Collections.Generic;
using System;
namespace DeepEarth.Map.Core.Clustering
{
    public class ClusterViewModel : GeoItemViewModel
    {
        private Dictionary<ClusterViewModel, ClusterViewModel> clusteredItems;

        public void AddToCluster(ClusterViewModel child)
        {
            clusteredItems.Add(child, child);           
            child.isClustered = true;
            this.IsClustered = false;
        }

        public void RemoveFromCluster(ClusterViewModel child)
        {
            clusteredItems.Remove(child);
            child.IsClustered = false;
        }

        public bool Contains(ClusterViewModel child)
        {
            return clusteredItems.ContainsKey(child);
        }

        public List<ClusterViewModel> GetClusteredItems()
        {
            return clusteredItems.Keys.ToList();
        }

        public void ClearCluster()
        {
            this.clusteredItems.Iter(c => c.Key.IsClustered = false);
            this.clusteredItems.Clear();
        }

        public bool IsCluster
        {
            get
            {
                try
                {
                    return clusteredItems.Any();
                }
                catch
                {
                    // if this fails, clusteredItems is being manipulated. i.e. this is a cluster.
                    return true;
                }
            }
        }

        private bool isClustered;
        public bool IsClustered
        {
            get
            {
                return isClustered;
            }
            set
            {
                isClustered = value;
            }
        }

        public new object Data
        {
            get
            {
                return this.wrappedViewModel.Data;
            }
        }   

        // If the representative is made invisible, find a visible child and make that the cluster representative
        private void TransferCluster()
        {
            ClusterViewModel newClusterHead = null;

            double minDistance = double.MaxValue;

            foreach (var c in clusteredItems.Keys.Where(c => c.IsActive).ToList())
            {
                double distance = Location.GetAngularDistance(this.Position, c.Position);

                if (distance <= minDistance)
                {
                    newClusterHead = c;
                    minDistance = distance;
                }
            }

            this.ClearCluster();

            if (newClusterHead != null)
            {
                clusteredItems.Where(kvp => kvp.Key != newClusterHead).ToList().Iter(i => newClusterHead.AddToCluster(i.Key));
                //newClusterHead.AddToCluster(this);

                newClusterHead.IsVisible = true;

                newClusterHead.Notify();
            }

        }

        // This is a property to enable binding - not ideal because it is calculated
        public int ClusterSize
        {
            get
            {
                return GetClusterSize();
            }
        }

        public int GetClusterSize()
        {
            //return clusteredItems.Keys.ToList().Where(k => k.IsActive).Count() + 1;

            // if the enumeration changes from under us, return whatever we have so far.
            int counter = 1;
            try
            {
                foreach (var key in clusteredItems.Keys)
                {
                    if (key.IsActive)
                    {
                        counter++;
                    }
                }
            }
            catch
            {
            }

            return counter;
        }

        private GeoItemViewModel wrappedViewModel;
        public GeoItemViewModel WrappedViewModel
        {
            get
            {
                return wrappedViewModel;
            }
            set
            {
                wrappedViewModel = value;
                this.SelectedCommand = wrappedViewModel.SelectedCommand;
            }
        }


        protected override void ExecuteSelectedCommand()
        {
            List<object> clustered = new List<object>();

            var atomicFeatureRootVM = GeoItemViewModelHelper.FindAtomicFeatureRoot(this.wrappedViewModel);

            object selectedObject = null;
            if (atomicFeatureRootVM != null)
            {
                selectedObject = atomicFeatureRootVM.Data;
            }
            else
            {
                selectedObject = this.wrappedViewModel.Data;
            }

            if (selectedObject != null)
            {
                clustered.Add(selectedObject);

                ISelectable selectable = selectedObject as ISelectable;
                if (selectable != null && selectable.SelectionCommand != null)
                {
                    selectable.SelectionCommand.Execute(selectedObject);
                }
            }


            foreach (var vm in clusteredItems.Keys)
            {
                var atomicFeature = GeoItemViewModelHelper.FindAtomicFeatureRoot(vm.WrappedViewModel);

                if (atomicFeature != null)
                {
                    clustered.Add(atomicFeature.Data);
                }
                else
                {
                    clustered.Add(vm.wrappedViewModel.Data);
                }
            }

            if (SelectedCommand != null && SelectedCommand.CanExecute(this))
            {
                SelectedCommand.Execute(clustered);
            }
        }


        public ClusterViewModel(GeoItemViewModel wrappedViewModel)
            : base()
        {
            this.WrappedViewModel = wrappedViewModel;
            clusteredItems = new Dictionary<ClusterViewModel, ClusterViewModel>();
            wrappedViewModel.PropertyChanged += new PropertyChangedEventHandler(wrappedViewModel_PropertyChanged);
        }

        // if we're now invisible, make sure we're not the view model used as the representative to the cluster
        void wrappedViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "IsActive":
                    {
                        if (!this.IsActive)
                        {
                            if (this.IsCluster)
                            {
                                TransferCluster();
                            }
                            this.IsVisible = false;
                        }
                        else
                        {
                            if (!this.IsClustered || this.IsCluster)
                            {
                                this.IsVisible = true;
                            }
                        }
                    }
                    break;

                case "ShowDetails":
                    NotifyPropertyChanged("ShowDetails");
                    break;
            }

        }

        private bool isVisible = true;
        public bool IsVisible
        {
            get
            {
                return isVisible;
            }
            set
            {
                if (isVisible != value)
                {
                    isVisible = value;
                    NotifyPropertyChanged("IsVisible");
                }
            }
        }

        public bool WrappedViewModelVisible
        {
            get
            {
                bool wrappedVisibility = this.WrappedViewModel.IsActive;

                if (wrappedVisibility)
                {
                    bool hierarchyVisible = true;

                    GeoItemViewModel current = this.WrappedViewModel;
                    while (current.Parent != null)
                    {
                        hierarchyVisible = current.Parent.IsActive;
                        current = current.Parent;

                        if (!hierarchyVisible)
                        {
                            break;
                        }
                    }

                    return hierarchyVisible;
                }
                else
                {
                    return false;
                }

            }
        }

        #region overrides
        public override Location Position
        {
            get
            {
                return this.WrappedViewModel.Position;
            }
            set
            {
                throw new Exception("Cannot set position of a cluster");
            }
        }
        public override bool IsActive
        {
            get
            {
                return this.WrappedViewModel.IsActive;
            }
        }

        public override bool ShowDetails
        {
            get
            {
                return WrappedViewModel.ShowDetails;
            }
        }

        public override LocationRectangle BoundingBox
        {
            get
            {
                return wrappedViewModel.BoundingBox;
            }
        }

        #endregion
        public void Notify()
        {
            NotifyPropertyChanged("ClusterSize");
            NotifyPropertyChanged("IsCluster");
        }

    }
}
